你必须知道的单片机存储器的那些事!
点击上方“果果小师弟”,选择“置顶/星标公众号”
干货福利,第一时间送达!
EEPROM AT24C02存储器
操作任何的IIC设备一般都要知道从机地址,也就是利用单片机操作读写的那个设备的地址。一般来说对于IIC设备地址是7位,其中高 4 位固定为:1010 b,低 3 位则由 A0/A1/A2信号线的电平决定。所以一个IIC总线上可以挂载2^3=8个EEPROM芯片,当然一般一个单片机只有一块EEPROM芯片,所以我们直接把这个A2A1A0接地即可,当然接VCC也没有问题,如果接GND那么地址就是1010000(0X50),如果接VCC那么地址就是1010111(0X57)。
总结:
存储量少,用起来方便 可以任意访问地址数据,每一个存储单片可以独立访问, 写入前是不需要对写入的单片做独立的擦除
FLASH W25Q128存储器
FLSAH字面意思就是闪现、一瞬间的意思,所以FLSAH存储器又称闪存,与 EEPROM都是掉电后数据不丢失的存储器,但FLASH存储器容量普遍大于 EPROM,现在基本取代了它的地位。生活中常用的 U 盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而 EEPROM可以单个字节擦写。
FLASH 芯片的最小擦除单位为扇区(Sector),而一个块(Block)包含 16 个扇区,4Kbytes为一个Sector,16个扇区为1个Block。W25Q64 容量为8M字节(即 64M bit), 分为128块(Block),每一块的大小为64K字节,每块又分为16个扇区(Sector),那么每个扇区就是4K个字节。W25Q128 容量为16M字节(即 128M bit),分为256块(Block),每一块的大小为64K字节,每块又分为16个扇区(Sector),那么每个扇区就是4K个字节(4096个字节,也就是4096个存储单元)。
* Function Name : SPI_Flash_Write
* Description : 在指定地址开始写入指定长度的数据,该函数带擦除操作!
* Input : *pBuffer:要写入数据的指针
WriteAddr:开始写入的地址(24bit)
NumByteToWrite:要写入的字节数(最大16 x 1024 x 1024)
* Output : None
* Return : None
****************************************************************************/
void SPI_Flash_Write(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % 4096;//mod运算求余,若writeAddr是4096整数倍,运算结果Addr值为0
NumOfPage = NumByteToWrite / 4096;//计算出要写多少整数扇区
NumOfSingle = NumByteToWrite % 4096;//mod运算求余,计算出剩余不满一扇区的字节数
count = 4096 - Addr;//差count个数据值,刚好可以对齐到扇区地址
if (Addr == 0)//Addr=0,则WriteAddr刚好按扇区对齐或者说小于一个扇区
{
//NumByteToWrite < 4096,写入的字符串大小长度小于一个扇区(4096个字节)的大小,如22
if (NumOfPage == 0)
{
SPI_Flash_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
else //NumByteToWrite > 4096,写入的字符串大小长度大与一个扇区(4096个字节)的大小,如4098
{
//先把整数扇区都写了
while (NumOfPage--)
{
SPI_Flash_Write_Page(pBuffer, WriteAddr, 4096);
WriteAddr += 4096;
pBuffer += 4096;
}
//若有多余的不满一扇区的数据,把它写完
SPI_Flash_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
//若地址与 4096 不对齐
else //Addr不等于0,则要写入的WriteAddr地址与4096不对齐
{
//NumByteToWrite < 4096
if (NumOfPage == 0)//大小不够一个扇区,如22
{
//当前页剩余的count个位置比NumOfSingle小,一扇区写不完
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
//先写满当前扇区
SPI_Flash_Write_Page(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
//再写剩余的数据
SPI_Flash_Write_Page(pBuffer, WriteAddr, temp);
}
else //当前扇区剩余的count个位置能写完NumOfSingle个数据
{
SPI_Flash_Write_Page(pBuffer, WriteAddr, NumByteToWrite);
}
}
else //NumByteToWrite > 4096 //大小够一个扇区,而且还超出一点点,如4098
{
//地址不对齐多出的count分开处理,不加入这个运算
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / 4096;
NumOfSingle = NumByteToWrite % 4096;
//先写完count个数据,为的是让下一次要写的地址对齐
SPI_Flash_Write_Page(pBuffer, WriteAddr, count);
//接下来就重复地址对齐的情况 */
WriteAddr += count;
pBuffer += count;
//把整数扇区都写了*/
while (NumOfPage--)
{
SPI_Flash_Write_Page(pBuffer, WriteAddr, 4096);
WriteAddr += 1096;
pBuffer += 4096;
}
//若有多余的不满一扇区的数据,把它写完
if (NumOfSingle != 0)
{
SPI_Flash_Write_Page(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}总结:
* Function Name : SPI_Flash_Read
* Description : 在指定地址开始读取指定长度的数据
* Input : *pBuffer:存储读出数据的指针
ReadAddr:开始读取的地址(24bit)
NumByteToRead:要读取的字节数(最大 16 x 1024 x 1024)
* Output : None
* Return : None
************************************************************************/
void SPI_Flash_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{
u16 i;
SPI_FLASH_CS=0; //使能器件
SPI1_ReadWriteByte(CMD_W25X_ReadData); //发送读取命令
SPI1_ReadWriteByte((ReadAddr& 0xFF0000)>>16); //发送扇区地址的高8bit
SPI1_ReadWriteByte((ReadAddr& 0xFF00)>>8); //发送扇区地址的中间8bit
SPI1_ReadWriteByte( ReadAddr& 0xFF); //发送扇区地址的低8bit
for(i=0;i<NumByteToRead;i++)
{
pBuffer[i]=SPI1_ReadWriteByte(0XFF); //循环读数
}
SPI_FLASH_CS=1; //取消片选
}
存储量大 不能任意访问字节地址数据,每一个存储单片不可以独立访问,最小读取单元是一个扇区 写入前是必须对写入的扇区做独立的擦除操作。擦除的目的是使存储单元的数据全为1
SD卡大容量存储器
SD 卡(Secure Digital Memory Card)在我们生活中已经非常普遍了,控制器对 SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI接口,另外一种是 SDIO 接口。SDIO全称是安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O 卡(专指使用SDIO 接口的一些输入输出设备)都可使用 SDIO 接口通讯。STM32F10x 系列控制器有一
个 SDIO 主机接口,它支持与上述使用 SDIO 接口的设备进行数据传输。
STM32F10x 系列控制器只支持 SD 卡规范版本 2.0,即只支持标准容量SD和高容量 SDHC 标准卡,不支持超大容量 SDXC 标准卡,所以可以支持的最高卡容量是 32GB。SD 卡一般都支持 SDIO 和 SPI 这两种接口。另外,STM32F42x 系列控制器的 SDIO 是不支持 SPI通信模式的,如果需要用到 SPI通信只能使用 SPI外设。因为SPI通信方式操作SD卡的数据线只有一根,而如果用SDIO的通信方式操作SD卡的数据线却又3根。为了节省资源一般在STM32F10x 系列控制器上用SPI的通信方式,而在引脚资源比较多的F4系列上就用SDIO的通信方式了。
SD容量有8MB、16MB、32MB、64MB、128MB、256MB、512MB、1GB、2GB (磁盘格式FAT12、FAT16) SDHC容量有2GB、4GB、8GB、16GB、32GB(磁盘格式FAT32) SDXC容量有32GB、48GB、64GB、128GB、256GB(磁盘格式exFAT)
3.1初始化
1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、上电延时(>74 个 CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58 和 CMD1 等);
6、取消片选,发多 8个CLK,结束初始化
* Function Name : SD_Init
* Description : 初始化SD卡
* Input : None
* Output : None
* Return : u8
* 0:NO_ERR
* 1:TIME_OUT
* 99:NO_CARD
*******************************************************************************/
u8 SD_Init(void)
{
u8 r1; // 存放SD卡的返回值
u16 retry; // 用来进行超时计数
u8 buf[4];
u16 i;
SD_SPI_Init(); //初始化IO
SD_SPI_SpeedLow(); //设置到低速模式
//先产生至少74个脉冲,让SD卡自己初始化完成
for(i=0;i<10;i++)
{
SD_SPI_WriteByte(0XFF);////80clks
}
//-----------------SD卡复位到idle开始-----------------
//循环连续发送CMD0,直到SD卡返回0x01,进入IDLE状态
//超时则直接退出
retry=0;
do
{
r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态,作用是让SD卡进入SPI模式。这里的CRC校验位0x95是固定的,不能修改
retry++;
}while((r1!=0X01) && (retry<20));//如果 SD 卡有正确的回应,代码就继续执行,如果没有回应程序就终止执行。
//跳出循环后,检查原因:初始化成功?or 重试超时?
if(retry==20) return 1; //超时返回1
SD_Type=0;//默认无卡
//下面是V2.0卡的初始化
//其中需要读取OCR数据,判断是SD2.0还是SD2.0HC卡
if(r1==0X01)
{
if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
{
//V2.0的卡,CMD8命令后会传回4字节的数据,要跳过再结束本命令
buf[0]=SD_SPI_ReadByte(); //should be 0x00
buf[1]=SD_SPI_ReadByte(); //should be 0x00
buf[2]=SD_SPI_ReadByte(); //should be 0x01
buf[3]=SD_SPI_ReadByte(); //should be 0xAA
if(buf[2]==0X01&&buf[3]==0XAA)//判断卡是否支持2.7~3.6V的电压范围
{
retry=0XFFFE;
//发卡初始化指令CMD55+CMD41
do
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41
}while(r1&&retry--);
//初始化指令发送完成,接下来获取OCR信息
//-----------鉴别SD2.0卡版本开始-----------
if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
{
//读OCR指令发出后,紧接着是4字节的OCR信息
buf[0]=SD_SPI_ReadByte();
buf[1]=SD_SPI_ReadByte();
buf[2]=SD_SPI_ReadByte();
buf[3]=SD_SPI_ReadByte();
//检查接收到的OCR中的bit30位(CCS),确定其为SD2.0还是SDHC
//如果CCS=1:为SDV2.0HC的2.0高容量卡 CCS=0:为SDV2.0的2.0版本的标准卡
if(buf[0]&0x40)
SD_Type=SD_TYPE_V2HC; //检查CCS
else
SD_Type=SD_TYPE_V2;
LCD_ShowNum(164,250,SD_Type,5,16);//显示SD卡容量
//-----------鉴别SD2.0卡版本结束-----------
}
}
}
//如果卡片版本信息是v1.0版本的,即r1=0x05,则进行以下初始化
else//SD V1.0/ MMC V3
{
//先发CMD55,应返回0x01;否则出错
r1 = SD_SendCmd(CMD55,0,0X01); //发送CMD55
if(r1 != 0x01)
return r1;
//得到正确响应后,发ACMD41,应得到返回值0x00
r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
if(r1<=1)
{
SD_Type=SD_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SD_SendCmd(CMD55,0,0X01); //发送CMD55
r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41
}while(r1&&retry--);
}else//MMC卡不支持CMD55+CMD41识别
{
SD_Type=SD_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SD_SendCmd(CMD1,0,0X01);//发送CMD1,发送MMC卡初始化命令
}while(r1&&retry--);
}
if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)
SD_Type=SD_TYPE_ERR;//错误的卡
}
}
SD_DisSelect();//取消片选
SD_SPI_SpeedHigh();//高速
if(SD_Type) return 0;
else if(r1) return r1;
return 0xaa;//其他错误
}
3.2写数据通过 CMD24实现
1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌 0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;
* Function Name : SD_WriteDisk
* Description : 向SD卡写数据
* Input : buf:数据缓存区
* sector:扇区
* cnt:扇区数
* Output : None
* Return : u8
* 0:ok
* 其他,失败.
*******************************************************************************/
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址
if(cnt==1)
{
r1=SD_SendCmd(CMD24,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SD_SendBlock(buf,0xFE);//写512个字节
}
}else
{
if(SD_Type!=SD_TYPE_MMC)
{
SD_SendCmd(CMD55,0,0X01);
SD_SendCmd(CMD23,cnt,0X01);//发送指令
}
r1=SD_SendCmd(CMD25,sector,0X01);//连续读命令
if(r1==0)
{
do
{
r1=SD_SendBlock(buf,0xFC);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
r1=SD_SendBlock(0,0xFD);//接收512个字节
}
}
SD_DisSelect();//取消片选,释放SPI总线
return r1;
}
3.3读取数据通过 CMD17实现
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌 0XFE;
4、接收数据;
5、接收2个字节的 CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
/*******************************************************************************
* Function Name : SD_ReadDisk
* Description : 读SD卡数据
* Input : buf:数据缓存区
* sector:扇区
* cnt:扇区数
* Output : None
* Return : u8
* 0:ok
* 其他,失败.
*******************************************************************************/
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
if(cnt==1)
{
r1=SD_SendCmd(CMD17,sector,0X01);//读命令
if(r1==0)//指令发送成功
{
r1=SD_RecvData(buf,512);//接收512个字节
}
}else
{
r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
do
{
r1=SD_RecvData(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
SD_SendCmd(CMD12,0,0X01); //发送停止命令
}
SD_DisSelect();//取消片选
return r1;
}
End